Skip to content

feat!: produce a dual ESM/CJS build#401

Open
rhamzeh wants to merge 20 commits into
developfrom
feat/esm-modules
Open

feat!: produce a dual ESM/CJS build#401
rhamzeh wants to merge 20 commits into
developfrom
feat/esm-modules

Conversation

@rhamzeh

@rhamzeh rhamzeh commented May 7, 2026

Copy link
Copy Markdown
Member

Description

This, coupled with dropping axios, should address #17 #72

What problem is being solved?

How is it being solved?

What changes are made to solve it?

References

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Inline async pool to remove it as the only CJS-style required runtime dependency.

Info: about tiny-async-pool:

rhamzeh and others added 16 commits March 31, 2026 09:01
The global fetch API is built into Node 20+, browsers, Deno, Cloudflare
Workers, and Vercel Edge.

On modern versions of node taht use undici internally, this should result
in improved perfomance.

BREAKING CHANGE: The `$response` property type changes from
`AxiosResponse<T>` to `FgaResponse<T>`. The constructor
now accepts an optional `HttpClient` instead of `AxiosInstance`.
`baseOptions.httpAgent`/`httpsAgent` are no longer applicable as fetch
handles connection pooling natively.
…T" to avoid undefined request method, streaming timeout kills long-running streams
…cts. With fetch, createStreamingRequestFunction returns the raw ReadableStream directly — $response is never set, so the fallback was the only path that ever executed.
…rror properties

Overwriting .constructor and .name on an error doesn't actually change its type, so instanceof checks failed for non-401/403 token endpoint errors.
…d of mutating error properties"

This reverts commit e2e8878.
Inline async pool to remove it as the only CJS-style required runtime dependency.

Info: about tiny-async-pool:
- URL: https://github.com/rxaviers/async-pool
- License: MIT
- Author: Rafael Xavier de Souza | https://github.com/rxaviers
Separates crypto-free utilities from generate-random-id.ts (which imports from "crypto").
This allows consumers to import from utils/utils-lite without pulling in the crypto dependency.

We faced this issue recently when we tried to use assertNever from this lib in syntaxt-transformer
@rhamzeh
rhamzeh requested review from a team as code owners May 7, 2026 05:24
@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d98824d-4e5c-495f-9b93-2d59e94c8bf4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/esm-modules

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rhamzeh
rhamzeh marked this pull request as draft May 7, 2026 05:24
@codecov-commenter

codecov-commenter commented May 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.78947% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.24%. Comparing base (4b43e0f) to head (6eb83c3).

Files with missing lines Patch % Lines
utils/generate-random-id.ts 28.57% 5 Missing ⚠️
utils/utils-lite/async-pool.ts 85.71% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #401      +/-   ##
===========================================
- Coverage    86.41%   86.24%   -0.17%     
===========================================
  Files           25       27       +2     
  Lines         1354     1374      +20     
  Branches       263      263              
===========================================
+ Hits          1170     1185      +15     
- Misses         111      116       +5     
  Partials        73       73              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

emilic added 2 commits May 8, 2026 07:21
Conflicts:
* errors.ts — main brought in PR #329's "stop mutating token refresh
  errors into auth errors" fix. Adapted for the fetch-era branch:
  FgaApiAuthenticationError now accepts HttpErrorContext | FgaApiError.
  When given an existing FgaApiError (re-wrap path from credentials/),
  it COPIES fields and preserves the original stack — the canonical fix
  for the audit-confirmed `instanceof` mutation P0. The HttpErrorContext
  path is unchanged. Also added the optional `context?: { clientId,
  audience, grantType }` second parameter from main so callers can
  override extracted values.
* package.json — took main's bumped @opentelemetry/api ^1.9.1; left
  axios out (the whole point of this branch).
* package-lock.json — regenerated via `npm install` after resolving
  package.json (don't hand-merge lockfiles).

Removed two helper functions (parseRequestData, getAuthenticationErrorMessage)
that main introduced for the AxiosError path; the fetch branch doesn't have
AxiosError as a type, and the helpers had no other callers.
Pulls the fetch-based HTTP client + the FgaApiAuthenticationError
re-wrap path (PR #329) into the WIP ESM/CJS dual-build branch.

Conflicts:
* package.json — kept ESM build setup (main / module / exports fields,
  build:cjs / build:esm / build:post scripts) AND took drop-axios's
  newer dep versions inherited from main: jest 30.3.0, ts-jest 29.4.9,
  eslint ^10.3.0, typescript ^6.0.3, nock ^14.0.14, @types/node ^25.6.0,
  @typescript-eslint/* ^8.59.2, @opentelemetry/api ^1.9.1. Removed
  tiny-async-pool + @types/tiny-async-pool (this branch already
  replaced tiny-async-pool with a native impl).

* package-lock.json — regenerated via \`npm install\`. 0 axios entries,
  0 tiny-async-pool entries, 0 vulnerabilities.

* tests/apiExecutor.test.ts, tests/credentials.test.ts,
  tests/headers.test.ts — pure import-style conflicts. Kept this
  branch's \`.js\` extensions on every relative import (mandatory for
  ESM resolution) and drop-axios's default \`import nock from "nock"\`
  shape (nock 14's main entry is callable only via the default export;
  \`import * as nock\` works for namespace methods but breaks
  \`nock(url)\` calls). credentials.test.ts also kept the
  FgaApiAuthenticationError import that PR #329's tests need
  (it was lost on this branch when feat/esm-modules forked from
  the pre-#329 base).

Files auto-merged: credentials/credentials.ts, errors.ts (which now
includes PR #329's re-wrap branch in FgaApiAuthenticationError —
typed \`HttpErrorContext | FgaApiError\` with optional context arg),
tests/client.test.ts, tests/index.test.ts, tests/jest.config.js,
tests/helpers/nocks.ts, tsconfig.json.

Build (cjs + esm) clean. Tests: 302 passed across 15 suites.
@rhamzeh rhamzeh changed the title feat: produce a dual ESM/CJS build feat!: produce a dual ESM/CJS build May 20, 2026
Base automatically changed from feat/drop-axios to develop June 30, 2026 14:40
Resolved conflicts by keeping the ESM-branch side in all cases:
- .js extensions on relative imports (required for ESM resolution)
- native utils/utils-lite/async-pool over tiny-async-pool dependency
- ESM/CJS dual-build package.json fields
- README Supported Runtimes section + ESM import examples

package-lock.json regenerated via npm install (0 axios, 0 tiny-async-pool).
Build (cjs+esm) clean. Tests: 302 passed across 15 suites.
@SoulPancake
SoulPancake marked this pull request as ready for review July 27, 2026 08:41
Copilot AI review requested due to automatic review settings July 27, 2026 08:41
@socket-security

socket-security Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
License policy violation: npm caniuse-lite under CC-BY-4.0

License: CC-BY-4.0 - The applicable license policy does not permit this license (5) (npm metadata)

License: CC-BY-4.0 - The applicable license policy does not permit this license (5) (package/package.json)

License: CC-BY-4.0 - The applicable license policy does not permit this license (5) (package/LICENSE)

From: package-lock.jsonnpm/jest@30.3.0npm/ts-jest@29.4.9npm/caniuse-lite@1.0.30001806

ℹ Read more on: This package | This alert | What is a license policy violation?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Find a package that does not violate your license policy or adjust your policy to allow this package's license.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/caniuse-lite@1.0.30001806. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the SDK packaging to ship dual CommonJS + ES Module builds, adjusting internal import specifiers to be ESM-friendly and removing tiny-async-pool as a runtime dependency by inlining a compatible implementation.

Changes:

  • Produce separate build outputs (dist/cjs, dist/esm) and publish them via package.json exports (plus post-build type markers).
  • Update source + tests to use explicit .js specifiers for ESM compatibility.
  • Inline lightweight utility helpers (including asyncPool, chunkArray, header/property helpers) to reduce runtime dependencies.

Reviewed changes

Copilot reviewed 38 out of 44 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
validation.ts Switch import specifiers to .js for ESM output compatibility.
utils/utils-lite/assert-never.ts New lightweight utility export.
utils/utils-lite/async-pool.ts Inline async concurrency pool to replace external dependency.
utils/utils-lite/chunk-array.ts New chunking helper used by batching logic.
utils/utils-lite/set-header-if-not-set.ts New header helper extracted into “lite” utils.
utils/utils-lite/set-not-enumerable-property.ts New helper to define non-enumerable properties.
utils/utils-lite/index.ts Barrel export for the new lite utilities.
utils/index.ts Re-export from utils-lite + keep generate-random-id.
utils/generate-random-id.ts Adjust random ID generation behavior (now always returns a string).
tsconfig.json Emit CJS JS into dist/cjs while placing declarations under dist.
tsconfig.esm.json New ESM build config emitting into dist/esm.
tests/validation.test.ts Update imports to .js to match ESM-style specifiers.
tests/telemetry/metrics.test.ts Update imports to .js to match ESM-style specifiers.
tests/telemetry/histograms.test.ts Update imports to .js to match ESM-style specifiers.
tests/telemetry/counters.test.ts Update imports to .js to match ESM-style specifiers.
tests/telemetry/configuration.test.ts Update imports to .js to match ESM-style specifiers.
tests/telemetry/attributes.test.ts Update imports to .js to match ESM-style specifiers.
tests/streaming.test.ts Update imports to .js to match ESM-style specifiers.
tests/jest.config.js Map .js specifiers back to TS sources for Jest resolution.
tests/index.test.ts Update imports to .js to match ESM-style specifiers.
tests/helpers/nocks.ts Update imports to .js to match ESM-style specifiers.
tests/helpers/index.ts Update helper re-exports to .js.
tests/helpers/default-config.ts Update imports to .js to match ESM-style specifiers.
tests/headers.test.ts Update imports to .js to match ESM-style specifiers.
tests/fetch-http-client.test.ts Update imports to .js to match ESM-style specifiers.
tests/errors.test.ts Update imports to .js to match ESM-style specifiers.
tests/errors-authentication.test.ts Update imports to .js to match ESM-style specifiers.
tests/credentials.test.ts Update imports to .js to match ESM-style specifiers.
tests/client.test.ts Update imports to .js to match ESM-style specifiers.
tests/apiExecutor.test.ts Update imports to .js to match ESM-style specifiers.
telemetry/metrics.ts Update internal imports to .js for ESM output compatibility.
telemetry/configuration.ts Update internal imports to .js for ESM output compatibility.
README.md Document CJS vs ESM usage and supported runtimes.
package.json Add dual entrypoints + exports; split build into CJS/ESM + post step.
package-lock.json Remove tiny-async-pool (and related typings); lockfile refresh.
index.ts Update all exports to .js specifiers for ESM output compatibility.
errors.ts Update import specifier + add HttpErrorContext interface.
credentials/index.ts Update re-exports to .js.
credentials/credentials.ts Update internal imports to .js for ESM output compatibility.
configuration.ts Update internal imports to .js for ESM output compatibility.
common.ts Update internal imports to .js for ESM output compatibility.
client.ts Replace external tiny-async-pool require with in-repo asyncPool.
base.ts Update internal imports to .js for ESM output compatibility.
api.ts Update internal imports to .js for ESM output compatibility.
Comments suppressed due to low confidence (1)

errors.ts:33

  • HttpErrorContext is declared twice, which will cause a TypeScript compile error (duplicate identifier). Remove the duplicate interface definition and keep a single source of truth.
/**
 * Context extracted from a failed HTTP request/response,
 * used to construct SDK error classes without coupling to any HTTP library.
 */
export interface HttpErrorContext {
  status?: number;
  statusText?: string;
  headers?: Record<string, string>;
  data?: any;
  requestUrl?: string;
  requestMethod?: string;
  requestData?: any;
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tsconfig.esm.json
Comment on lines +3 to +9
"compilerOptions": {
"module": "es2020",
"moduleResolution": "bundler",
"outDir": "dist/esm",
"declaration": false,
"declarationDir": null
}
Comment on lines +33 to +49
/**
* Runs async functions in a limited concurrency pool.
* Yields results as they complete, in order of input.
* Rejects immediately if any promise rejects.
*
* @param concurrency - Maximum number of concurrent executions (>= 1)
* @param iterable - Input items to process
* @param iteratorFn - Async function to apply to each item
*/
export async function* asyncPool<IN, OUT>(
concurrency: number,
iterable: Iterable<IN>,
iteratorFn: (item: IN) => Promise<OUT>,
): AsyncGenerator<OUT> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const executing = new Set<Promise<any>>();

Comment on lines +21 to +24
import { TelemetryCounters } from "../telemetry/counters.js";
import { TelemetryConfiguration } from "../telemetry/configuration.js";
import { randomUUID } from "crypto";
import SdkConstants from "../constants";
import SdkConstants from "../constants/index.js";
Comment on lines 3 to +7
/**
* Generates a random ID
*
* Note: May not return a valid value on older browsers - we're fine with this for now
* Note: May not return a secure random value.
* We're fine with this, as this is just used to identify requests.
- brace-expansion ^5.0.8 (fixes GHSA-mh99-v99m-4gvg high-severity DoS,
  advisory range <=5.0.7 also affects develop's lockfile)
- @babel/core ^7.29.7 (fixes GHSA-4x5r-pxfx-6jf8 arbitrary file read)

npm audit: 0 vulnerabilities. Build (cjs+esm) clean, 302 tests pass.
Copilot AI review requested due to automatic review settings July 27, 2026 08:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 44 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

tsconfig.esm.json:9

  • declarationDir expects a string path; setting it to null will make tsc --project tsconfig.esm.json fail to parse/validate compiler options. Since declarations are disabled for the ESM build, this option should be removed.
    utils/utils-lite/async-pool.ts:38
  • The docstring says results are yielded "in order of input", but this implementation yields in completion order via Promise.race. Also, concurrency is documented as ">= 1" but isn’t validated, which can lead to confusing behavior when callers pass 0/negative values.

Comment thread errors.ts
Comment on lines +7 to +19
/**
* Context extracted from a failed HTTP request/response,
* used to construct SDK error classes without coupling to any HTTP library.
*/
export interface HttpErrorContext {
status?: number;
statusText?: string;
headers?: Record<string, string>;
data?: any;
requestUrl?: string;
requestMethod?: string;
requestData?: any;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants